package query.select

import ast.statement.select.SqlUnionType
import database.DBConnection
import expr.QuerySub
import expr.QueryTableColumn
import java.math.BigDecimal
import java.sql.Connection
import java.util.*
import kotlin.reflect.KProperty
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.declaredMembers
import kotlin.reflect.jvm.javaField

abstract class SelectQueryImpl : SelectQuery {
    override infix fun union(select: SelectQuery): UnionSelect {
        return UnionSelect(this, SqlUnionType.UNION, select, this.getDbType())
    }

    override infix fun unionAll(select: SelectQuery): UnionSelect {
        return UnionSelect(this, SqlUnionType.UNION_ALL, select, this.getDbType())
    }

    fun query(
        sql: String,
        isTransaction: Boolean,
        conn: Connection,
        dbConnection: DBConnection?
    ): List<Map<String, Any?>> {
        return if (isTransaction) {
            database.query(conn, sql)
        } else {
            val connection = if (conn.isClosed) {
                dbConnection?.getConnection()
            } else {
                conn
            }
            val tempResult = database.query(connection!!, sql)
            connection.close()
            tempResult
        }
    }

    fun queryCount(
        sql: String,
        isTransaction: Boolean,
        conn: Connection,
        dbConnection: DBConnection?
    ): Long {
        val result = if (isTransaction) {
            database.queryCount(conn, sql)
        } else {
            val connection = if (conn.isClosed) {
                dbConnection?.getConnection()
            } else {
                conn
            }
            val tempResult = database.queryCount(connection!!, sql)
            connection.close()
            tempResult
        }

        return result.toLong()
    }

    @Suppress("UNCHECKED_CAST")
    fun <T : Any> bind(clazz: Class<T>, result: List<Map<String, Any?>>): List<T> {
        when (clazz) {
            Int::class.java, Long::class.java, Float::class.java, Double::class.java, String::class.java, Date::class.java, BigDecimal::class.java -> {
                return result.map {
                    val keys = it.keys.toList()
                    val key = keys[0]
                    it[key] as T
                }
            }

            Pair::class.java -> {
                return result.map {
                    val keys = it.keys.toList()
                    (it[keys[0]] to it[keys[1]]) as T
                }
            }

            Triple::class.java -> {
                return result.map {
                    val keys = it.keys.toList()
                    Triple(it[keys[0]], it[keys[1]], it[keys[2]]) as T
                }
            }
        }


        val companion = clazz.kotlin.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val columns = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) to it.name }
            .filter { it.first is QueryTableColumn }
            .map { (it.first as QueryTableColumn).column to it.second }
            .toMap()

        return result.map {
            val row = clazz.newInstance()

            columns.forEach { column ->
                val fieldName = column.value
                val field = (clazz.kotlin.declaredMembers.find { it.name == fieldName } as KProperty).javaField
                field?.isAccessible = true
                field?.set(row, it[column.key])
            }

            row
        }
    }

    inline fun <reified T> bind(result: List<Map<String, Any?>>): List<T> {
        when (T::class) {
            Int::class, Long::class, Float::class, Double::class, String::class, Date::class, BigDecimal::class -> {
                return result.map {
                    val keys = it.keys.toList()
                    val key = keys[0]
                    it[key] as T
                }
            }

            Pair::class -> {
                return result.map {
                    val keys = it.keys.toList()
                    (it[keys[0]] to it[keys[1]]) as T
                }
            }

            Triple::class -> {
                return result.map {
                    val keys = it.keys.toList()
                    Triple(it[keys[0]], it[keys[1]], it[keys[2]]) as T
                }
            }
        }

        val companion = T::class.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val columns = companionClass.declaredMemberProperties
            .map { it.getter.call(companion) to it.name }
            .filter { it.first is QueryTableColumn }
            .map { (it.first as QueryTableColumn).column to it.second }
            .toMap()

        return result.map {
            val rowClass = T::class
            val row = rowClass.java.newInstance()

            columns.forEach { column ->
                val fieldName = column.value
                val field = (rowClass.declaredMembers.find { it.name == fieldName } as KProperty).javaField
                field?.isAccessible = true
                field?.set(row, it[column.key])
            }

            row
        }
    }

    fun queryMap(): List<Map<String, Any?>> {
        return query(sql(), isTransaction, conn!!, dbConnection)
    }

    fun <T : Any> query(clazz: Class<T>): List<T> {
        val list = query(this.sql(), isTransaction, conn!!, dbConnection)

        return bind(clazz, list)
    }

    inline fun <reified T> query(): List<T> {
        val list = query(this.sql(), isTransaction, conn!!, dbConnection)

        return bind(list)
    }

    open fun fetchCount(): Long {
        return queryCount(sql(), isTransaction, conn!!, dbConnection)
    }

    open fun exist(): Boolean {
        return fetchCount() > 0
    }

    override fun toString(): String {
        return sql()
    }

    override fun toExpr(): QuerySub {
        return QuerySub(this)
    }
}